import hashlib
import os
import threading
import time
import tkinter as tk
from datetime import datetime
from tkinter import ttk, filedialog, messagebox, scrolledtext

import serial
import serial.tools.list_ports
import ttkbootstrap as ttk
import ymodem
from ttkbootstrap.constants import *

from app.util.annotation_util import annotate, ClassInfo
from app.view.base_view import View


@annotate(ClassInfo(name="DFU FSerialYmodem", category="ft_tool"))
class FtSerialYmodemUpdateView(View):
    """设置视图"""

    def __init__(self, parent, controller):
        super().__init__(parent, controller)
        # 初始化变量
        self.serial_port = None
        self.is_connected = False
        self.firmware_path = ""
        self.firmware_md5 = ""
        self.upgrade_in_progress = False
        self.cancel_requested = False

        # 创建UI
        self._create_widgets()

        # 初始搜索串口
        self.search_ports()

    def _create_widgets(self):
        """创建界面组件"""
        # 主容器
        main_container = ttk.Frame(self, padding=10)
        main_container.pack(fill=BOTH, expand=YES)

        # 顶部配置区域
        config_frame = ttk.LabelFrame(main_container, text="配置", padding=10)
        config_frame.pack(fill=X, pady=5)

        # 串口配置
        serial_frame = ttk.Frame(config_frame)
        serial_frame.pack(fill=X, pady=5)

        ttk.Label(serial_frame, text="串口:").pack(side=LEFT, padx=5)
        self.port_var = tk.StringVar()
        self.port_combo = ttk.Combobox(serial_frame, textvariable=self.port_var, width=15)
        self.port_combo.pack(side=LEFT, padx=5)

        ttk.Label(serial_frame, text="波特率:").pack(side=LEFT, padx=5)
        self.baudrate_var = tk.StringVar(value="115200")
        self.baudrate_combo = ttk.Combobox(
            serial_frame,
            textvariable=self.baudrate_var,
            values=["9600", "19200", "38400", "57600", "115200", "230400", "460800", "921600"],
            width=10
        )
        self.baudrate_combo.pack(side=LEFT, padx=5)

        ttk.Button(
            serial_frame,
            text="搜索串口",
            command=self.search_ports,
            bootstyle=INFO
        ).pack(side=LEFT, padx=5)

        self.connect_btn = ttk.Button(
            serial_frame,
            text="打开串口",
            command=self.toggle_connection,
            bootstyle=SUCCESS
        )
        self.connect_btn.pack(side=LEFT, padx=5)

        # 固件配置
        firmware_frame = ttk.Frame(config_frame)
        firmware_frame.pack(fill=X, pady=5)

        ttk.Label(firmware_frame, text="固件文件:").pack(side=LEFT, padx=5)
        self.firmware_var = tk.StringVar(value="未选择文件")
        ttk.Label(firmware_frame, textvariable=self.firmware_var).pack(side=LEFT, padx=5, fill=X, expand=YES)

        ttk.Button(
            firmware_frame,
            text="浏览",
            command=self.select_firmware,
            bootstyle=PRIMARY
        ).pack(side=LEFT, padx=5)

        # MD5校验值
        md5_frame = ttk.Frame(config_frame)
        md5_frame.pack(fill=X, pady=5)

        ttk.Label(md5_frame, text="MD5校验:").pack(side=LEFT, padx=5)
        self.md5_var = tk.StringVar(value="")
        ttk.Label(md5_frame, textvariable=self.md5_var).pack(side=LEFT, padx=5, fill=X, expand=YES)

        # 操作区域
        operation_frame = ttk.LabelFrame(main_container, text="操作", padding=10)
        operation_frame.pack(fill=X, pady=5)

        self.start_btn = ttk.Button(
            operation_frame,
            text="开始升级",
            command=self.start_upgrade,
            bootstyle=SUCCESS,
            state=DISABLED
        )
        self.start_btn.pack(side=LEFT, padx=10, pady=5)

        self.cancel_btn = ttk.Button(
            operation_frame,
            text="取消升级",
            command=self.cancel_upgrade,
            bootstyle=DANGER,
            state=DISABLED
        )
        self.cancel_btn.pack(side=LEFT, padx=10, pady=5)

        # 进度条
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(
            operation_frame,
            variable=self.progress_var,
            maximum=100,
            length=300
        )
        self.progress_bar.pack(side=LEFT, padx=10, pady=5, fill=X, expand=YES)

        # 日志区域
        log_frame = ttk.LabelFrame(main_container, text="日志", padding=10)
        log_frame.pack(fill=BOTH, expand=YES, pady=5)

        self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, state=DISABLED)
        self.log_text.pack(fill=BOTH, expand=YES)

        # 配置日志文本标签样式
        self.log_text.tag_configure("info", foreground="white")
        self.log_text.tag_configure("success", foreground="green")
        self.log_text.tag_configure("warning", foreground="#ffaa00")
        self.log_text.tag_configure("error", foreground="red")
        self.log_text.tag_configure("time", foreground="#66ccff")

    def log(self, message, level="info"):
        """添加日志信息"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.log_text.config(state=NORMAL)
        self.log_text.insert(tk.END, f"[{timestamp}] ", "time")
        self.log_text.insert(tk.END, f"{message}\n", level)
        self.log_text.config(state=DISABLED)
        self.log_text.see(tk.END)  # 滚动到最新日志

    def search_ports(self):
        """搜索可用串口"""
        self.log("正在搜索串口...")
        self.port_combo['values'] = []

        try:
            ports = serial.tools.list_ports.comports()
            port_list = [port.device for port in ports]

            if port_list:
                self.port_combo['values'] = port_list
                self.port_combo.current(0)
                self.log(f"找到 {len(port_list)} 个可用串口", "success")
            else:
                self.log("未找到可用串口", "warning")
        except Exception as e:
            self.log(f"搜索串口失败: {str(e)}", "error")

    def toggle_connection(self):
        """切换串口连接状态"""
        if self.is_connected:
            self.close_serial()
        else:
            self.open_serial()

    def open_serial(self):
        """打开串口"""
        port = self.port_var.get()
        baudrate = int(self.baudrate_var.get())

        if not port:
            self.log("请选择串口", "warning")
            return

        try:
            self.log(f"尝试打开串口 {port}，波特率 {baudrate}")
            self.serial_port = serial.Serial(
                port=port,
                baudrate=baudrate,
                timeout=1,
                write_timeout=1
            )

            if self.serial_port.is_open:
                self.is_connected = True
                self.connect_btn.config(text="关闭串口", bootstyle=DANGER)
                self.log(f"串口 {port} 已打开", "success")

                # 检查是否已选择固件，启用升级按钮
                if self.firmware_path:
                    self.start_btn.config(state=NORMAL)
        except Exception as e:
            self.log(f"打开串口失败: {str(e)}", "error")
            self.serial_port = None

    def close_serial(self):
        """关闭串口"""
        if self.serial_port and self.serial_port.is_open:
            try:
                self.serial_port.close()
                self.log(f"串口 {self.port_var.get()} 已关闭", "info")
            except Exception as e:
                self.log(f"关闭串口失败: {str(e)}", "error")

        self.is_connected = False
        self.connect_btn.config(text="打开串口", bootstyle=SUCCESS)
        self.start_btn.config(state=DISABLED)

    def select_firmware(self):
        """选择固件文件"""
        file_path = filedialog.askopenfilename(
            title="选择固件文件",
            filetypes=[("固件文件", "*.bin;*.hex;*.firmware"), ("所有文件", "*.*")]
        )

        if file_path:
            self.firmware_path = file_path
            self.firmware_var.set(os.path.basename(file_path))
            self.log(f"已选择固件: {os.path.basename(file_path)}")

            # 计算MD5
            self.calculate_md5()

            # 如果串口已连接，启用升级按钮
            if self.is_connected:
                self.start_btn.config(state=NORMAL)

    def calculate_md5(self):
        """计算固件文件的MD5值"""
        if not self.firmware_path or not os.path.exists(self.firmware_path):
            self.log("固件文件不存在", "error")
            return

        try:
            self.log("正在计算MD5校验值...")
            md5_hash = hashlib.md5()
            with open(self.firmware_path, "rb") as f:
                # 分块读取大文件
                for chunk in iter(lambda: f.read(4096), b""):
                    md5_hash.update(chunk)

            self.firmware_md5 = md5_hash.hexdigest()
            self.md5_var.set(self.firmware_md5)
            self.log(f"MD5校验值: {self.firmware_md5}", "success")
        except Exception as e:
            self.log(f"计算MD5失败: {str(e)}", "error")
            self.firmware_md5 = ""
            self.md5_var.set("")

    def start_upgrade(self):
        """开始升级流程"""
        if not self.is_connected:
            self.log("请先打开串口", "warning")
            return

        if not self.firmware_path or not os.path.exists(self.firmware_path):
            self.log("固件文件不存在", "error")
            return

        # 确认升级
        if not messagebox.askyesno("确认升级", "确定要开始升级吗？\n升级过程中请勿断开连接！"):
            return

        # 准备升级
        self.upgrade_in_progress = True
        self.cancel_requested = False
        self.start_btn.config(state=DISABLED)
        self.cancel_btn.config(state=NORMAL)
        self.connect_btn.config(state=DISABLED)
        self.progress_var.set(0)

        # 在后台线程中执行升级
        threading.Thread(target=self.perform_upgrade, daemon=True).start()

    def cancel_upgrade(self):
        """取消升级"""
        if not self.upgrade_in_progress:
            return

        if messagebox.askyesno("确认取消", "确定要取消升级吗？\n这可能会导致设备无法正常工作！"):
            self.log("用户请求取消升级", "warning")
            self.cancel_requested = True
            self.cancel_btn.config(state=DISABLED)

    def perform_upgrade(self):
        """执行升级流程"""
        try:
            # 1. 发送AT+UPDATE命令
            self.log("发送AT+UPDATE命令，请求设备进入升级模式...")
            self.serial_port.write(b"AT+UPDATE\r\n")

            # 等待设备响应
            response = self.wait_for_response(b"READY TO UPDATE", timeout=5)
            if not response:
                self.log("设备未响应升级请求", "error")
                self.finish_upgrade(False)
                return

            self.log("设备已进入升级模式", "success")
            self.update_progress(10)

            # 检查是否取消升级
            if self.cancel_requested:
                self.log("升级已取消", "warning")
                self.finish_upgrade(False)
                return

            # 2. 使用ymodem协议传输文件
            self.log("开始使用YModem协议传输固件...")

            # 创建YModem发送器
            def sender_getc(size, timeout=1):
                """YModem需要的接收函数"""
                if self.cancel_requested:
                    return None
                return self.serial_port.read(size) or None

            def sender_putc(data, timeout=1):
                """YModem需要的发送函数"""
                if self.cancel_requested:
                    return False
                return self.serial_port.write(data) == len(data)

            ymodem_sender = ymodem.YModem(sender_getc, sender_putc)

            # 发送文件
            with open(self.firmware_path, "rb") as f:
                file_size = os.path.getsize(self.firmware_path)
                file_name = os.path.basename(self.firmware_path)

                # 定义进度回调函数
                def progress_callback(total_packets, success_packets):
                    if total_packets == 0:
                        return
                    progress = 10 + (success_packets / total_packets) * 80  # 10%到90%
                    self.update_progress(progress)

                # 发送文件
                result = ymodem_sender.send(f, file_name=file_name, file_size=file_size, callback=progress_callback)

            # 检查是否取消升级
            if self.cancel_requested:
                self.log("升级已取消", "warning")
                self.finish_upgrade(False)
                return

            if not result:
                self.log("YModem文件传输失败", "error")
                self.finish_upgrade(False)
                return

            self.update_progress(90)
            self.log("固件传输完成，等待设备验证和升级...", "info")

            # 3. 等待升级完成
            response = self.wait_for_response(b"UPGRADE SUCCESS", timeout=30)
            if response:
                self.log("设备升级成功！", "success")
                self.update_progress(100)
                self.finish_upgrade(True)
            else:
                self.log("未收到升级成功确认", "error")
                self.finish_upgrade(False)

        except Exception as e:
            self.log(f"升级过程出错: {str(e)}", "error")
            self.finish_upgrade(False)

    def wait_for_response(self, expected, timeout=10):
        """等待设备响应"""
        start_time = time.time()
        response = b""

        while time.time() - start_time < timeout:
            if self.cancel_requested:
                return False

            if self.serial_port.in_waiting > 0:
                response += self.serial_port.read(self.serial_port.in_waiting)
                self.log(f"收到数据: {response.decode('utf-8', errors='ignore').strip()}", "info")

                if expected in response:
                    return True

            time.sleep(0.1)

        return False

    def update_progress(self, value):
        """更新进度条"""
        self.progress_var.set(value)
        self.update_idletasks()

    def finish_upgrade(self, success):
        """完成升级流程"""
        self.upgrade_in_progress = False
        self.cancel_requested = False

        # 恢复UI状态
        self.after(0, lambda: self.start_btn.config(
            state=NORMAL if self.is_connected and self.firmware_path else DISABLED))
        self.after(0, lambda: self.cancel_btn.config(state=DISABLED))
        self.after(0, lambda: self.connect_btn.config(state=NORMAL))

        if not success:
            self.after(0, lambda: self.log("升级失败", "error"))

    def create_widgets(self):
        pass
